// ##
#include "SqlDBInputBox.h"
// ## WINDOWS
#include <Windows.h>
// ## ODBC
#include <sql.h>
#include <sqlext.h>
// ## C++
#include <iostream>
#include <sstream>

namespace jm4bbx {

	//const char* IntSQLBoxDescriptor::BOX_ID = "ISQL";
	//const char* StringSQLBoxDescriptor::BOX_ID = "SSQL";
	
	SQLCHAR* jm4bbx::SqlDBInputBoxImpl::CONNECTION_STRING = 
		(SQLCHAR*)"DSN=Diplomka;";

	template<class T1, class T2>
	int initColumns(SQLHANDLE sqlstatementhandle, T1& types, T2& data) {
		// detekcia poctu stlpcov
		SQLSMALLINT columns = 0;
		SQLNumResultCols(sqlstatementhandle, &columns);
		// detekcia datovych typov stlpcov

		for (SQLSMALLINT i = 0; i < columns; ++i) {
			const int COL_LENGHT = 64;
			char colName[COL_LENGHT];
			SQLSMALLINT nameLenghtPtr = 0, dataTypetPtr = 0,
				decimalDigitsPtr = 0, nullablePtr = 0;
			SQLULEN columnSizePtr;

			SQLDescribeCol(sqlstatementhandle, i  + 1, (SQLCHAR*)colName, 
				COL_LENGHT, &nameLenghtPtr, &dataTypetPtr, &columnSizePtr, 
				&decimalDigitsPtr, &nullablePtr);

			types.push_back(dataTypetPtr);// Ulozim typ stlpca
			data.push_back(SqlDBInputBoxImpl::ColumnType());// zalozim strukturu pre stlpec
		}

		return columns;
	}

	template<int columnType, class T, int size, class CellType>
	void load(SQLHANDLE sqlstatementhandle, int columnIndex, 
		SqlDBInputBoxImpl::ColumnType& data) {
			T value;
			SQLGetData(sqlstatementhandle, columnIndex+1, columnType, &value, 
				size, NULL);
			BaseCellPtr newCell = BaseCellPtr(new CellType());
			static ConcreteCellValueSetterVisitor<CellType> visitorX;
			visitorX.setValue(value);
			newCell->acceptVisitor(&visitorX);
			data.push_back(newCell);
	}

	void loadColumn(int sqlType, SQLHANDLE sqlstatementhandle, int columnIndex, 
		SqlDBInputBoxImpl::ColumnType& x) { 
			switch (sqlType) {
			case SQL_VARCHAR:		
				load<SQL_CHAR, SqlDBInputBoxImpl::StringDataType, 
					SqlDBInputBoxImpl::STRING_DATA_SIZE, StringCell>(
						sqlstatementhandle, columnIndex, x);
				break;
			case SQL_INTEGER:
				load<SQL_INTEGER, SqlDBInputBoxImpl::IntegerDataType, 
					SqlDBInputBoxImpl::INTEGER_DATA_SIZE, IntCell>(
						sqlstatementhandle, columnIndex, x);
				break;
			default:
				// neimplemntovana podpora pre tento typ SQL dat
				assert(false); 
			}
	}

	void show_error(unsigned int handletype, const SQLHANDLE& handle){
		SQLCHAR sqlstate[1024];
		SQLCHAR message[1024];
		if(SQL_SUCCESS == SQLGetDiagRec(handletype, handle, 1, sqlstate, NULL, message, 1024, NULL))
			std::cerr <<"Message: "<<message<<"\nSQLSTATE: "<<sqlstate<<std::endl;
	}

	bool SqlDBInputBoxImpl::initEnviroment() {
		return SQL_SUCCESS == SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, 
			&enviromentHandle_) && SQL_SUCCESS == 
			SQLSetEnvAttr(enviromentHandle_, SQL_ATTR_ODBC_VERSION, 
			(SQLPOINTER)SQL_OV_ODBC3, 0);

	}

	bool SqlDBInputBoxImpl::connect() {

		auto retAllocHandle = SQLAllocHandle(SQL_HANDLE_DBC, enviromentHandle_, 
			&connectionHandle_);

		if (retAllocHandle != SQL_SUCCESS) return false;
		//SQLCHAR retconstring[1024];

		auto connect = SQLDriverConnect(connectionHandle_, NULL, 
			CONNECTION_STRING, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE);

		switch(connect) {
		case SQL_SUCCESS_WITH_INFO:
			show_error(SQL_HANDLE_DBC, connectionHandle_);
			return true;
		case SQL_INVALID_HANDLE:
		case SQL_ERROR:
			show_error(SQL_HANDLE_DBC, connectionHandle_);
			return false;
		default:
			break;
		}

		return true;
	}

	bool SqlDBInputBoxImpl::sendStatement(const std::string& statement) {
		return SQL_SUCCESS == SQLAllocHandle(SQL_HANDLE_STMT, connectionHandle_, 
			&statementHandle_) && SQL_SUCCESS == SQLExecDirect(statementHandle_, 
		 (SQLCHAR*)statement.c_str(), SQL_NTS);
	}


	void SqlDBInputBoxImpl::executeStatement(const std::string& statement) {


		if (initEnviroment()) {
			if (connect()) {
				if (sendStatement(statement)) {
					size_t columns = initColumns(statementHandle_, types_, data_);
					while(SQLFetch(statementHandle_)==SQL_SUCCESS){
						for (SQLSMALLINT i = 0; i < columns; ++i) {
							loadColumn(types_[i], statementHandle_, i, data_[i]);
						}
					}
				}
			}
		}
		
		releaseSQLHandles();
	}

	size_t SqlDBInputBoxImpl::getColumnSize() const {
		if (data_.size() > 0) {
			// vsetky stlpce su rovnako velke
			return data_[0].size();
		}
		else {
			return 0;
		}
	}

	void SqlDBInputBoxImpl::releaseSQLHandles() {
		SQLFreeHandle(SQL_HANDLE_STMT, statementHandle_ );
		SQLDisconnect(connectionHandle_);
		SQLFreeHandle(SQL_HANDLE_DBC, connectionHandle_);
		SQLFreeHandle(SQL_HANDLE_ENV, enviromentHandle_);
	}






} // namespace jm4bbx
